<head>
  <meta charset="UTF-8">
  <style>
    :root{
      filter:invert(16%);

    }
    body{
      background: rgb(0,0,0);
      color:rgb(250,250,250);
      font-family: sans-serif;
      
    }
    #content{
      /*background: rgb(0,0,0);*/
      position: absolute;
      left:calc(50% - 400px);
      width:800px;
    }
    h1{
      font-weight: normal;
      text-align:center;
    }
    h2{
      font-weight: normal;
      font-style: italic;
      font-size:18px;
      text-align:center;
    }
    pre {
      font-size:15px;
      white-space: pre-wrap;       /* Since CSS 2.1 */
      white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
      white-space: -pre-wrap;      /* Opera 4-6 */
      white-space: -o-pre-wrap;    /* Opera 7 */
      word-wrap: break-word;       /* Internet Explorer 5.5+ */
    }
    canvas{
      image-rendering: pixelated;
    }
  </style>
</head>
<body>
  <a href="https://github.com/LingDong-/skeleton-tracing" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#666; color:#000; position: absolute; top: 0; border: 0; left: 0; transform: scale(-1, 1);" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
  <div id="content">
    <h1>Skeleton Tracing</h1>
    <h2>
    A new algorithm for retrieving topological skeleton as a set of polylines from binary images.
    </h2>
    <br>
    <br>
    <select id="sel">
      <option>SELECT DEMO</option>
      <option value="1">Single Image</option>
      <option value="2">Webcam</option>
      <option value="3">Random CJK</option>
      <option value="4">Freehand</option>
      <option value="5">Vandalize</option>
    </select>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="checkbox" id="chk" checked> <small style="color:gray">Use WebAssembly Acceleration</small>
    <pre id="readme"></pre>
  </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/skeleton-tracing-wasm/build/trace_skeleton_wasm.js"></script>
<script>
  (async function loadTraceSkeletonWasm(){
    TraceSkeletonWASM = await TraceSkeleton.load();
  })();
</script>
<script src="https://cdn.jsdelivr.net/npm/skeleton-tracing-js/dist/trace_skeleton.min.js"></script>
<script>
  TraceSkeletonVanilla=TraceSkeleton;try{TraceSkeleton=TraceSkeletonWASM}catch(e){}
</script>
<script defer>
var TOP = 200;
var LEFT= "calc(50% - 400px)";
var DWIDTH = 800;
var is_safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

var canv = document.createElement("canvas");
var ctx = canv.getContext('2d');
canv.style = `position:absolute;left:${LEFT};top:${TOP}px;width:${DWIDTH}px;border:1px solid rgb(30,30,30)`;
document.body.appendChild(canv);

var div = document.createElement("div");
div.style = `position:absolute;left:${LEFT};top:${TOP}px;pointer-events:none`;
document.body.appendChild(div)

var animReq;

function fromImageUrl(url){
  var img = new Image();
  img.src = url;
  
  img.onload = function(){
    canv.width = img.width;
    canv.height = img.height;
    
    ctx.drawImage(img,0,0);
    var s = TraceSkeleton.fromCanvas(canv);
    var r = s.rects;
    var p = s.polylines;
    var v = TraceSkeleton.visualize(s,{scale:DWIDTH/img.width,strokeWidth:4});
    
    div.innerHTML = v;
  
  }
}

function fromWebcam(){
  var vid = document.createElement("video");
  vid.playsinline="playsinline"
  vid.autoplay="autoplay"
  navigator.mediaDevices.getUserMedia({audio:false,video:true}).then(function(stream){
    window.stream = stream;
    vid.srcObject = stream;
  })
  vid.style.position="absolute"
  vid.style.opacity=0;
  vid.style.pointerEvents="none";
  vid.style.zIndex = -1000;
  canv.width = vid.videoWidth = 360;
  canv.height = vid.videoHeight = 240;
  ctx.filter = "blur(5px)";

  document.body.appendChild(vid);

  function loop(){
    animReq = requestAnimationFrame(loop);
    // setTimeout(loop,1000);

    ctx.drawImage(vid,0,0,canv.width,canv.height);

    var imdata = ctx.getImageData(0,0,canv.width,canv.height);
    var data = imdata.data;
    var str = "";
    var avg = 0;
    var n = (canv.width*canv.height);
    for (var i = 0; i < data.length; i+=4){
      avg += data[i]/n;
    }
    for (var i = 0; i < data.length; i+=4){
      var x = (i/4)%canv.width;
      var y = Math.floor((i/4)/canv.width);

      var c = 0;
      if (is_safari){//stupid safari doesn't support blur
        for (var k = y-3; k <= y+3; k++){
          for (var l = x-3; l <= x+3; l++){
            c+=data[(k*canv.width+l)*4]
          }
        }
        c/=49;
      }else{
        var c = (data[i]+data[i+1]+data[i+2])/3;
      }
      

      // var d = 1-Math.sqrt(Math.pow(x-canv.width/2,2)+Math.pow(y-canv.height/2,2))/((canv.width+canv.height)/0.75);
      var g = Math.min(Math.max(c*1.5,128),255);
      if (x<1||x>canv.width-2||y<1||y>canv.height-2){
        c=0;
        g=c;
      }
      if (c > avg){
        str += "\1";
      }else{
        str += "\0";
        g = 0;
      }
      data[i]=g;
      data[i+1]=g;
      data[i+2]=g;
    }
    ctx.putImageData(imdata,0,0);

    var s = TraceSkeleton.fromCharString(str,canv.width,canv.height);
    // console.log(s)
    var v = TraceSkeleton.visualize(s,{scale:DWIDTH/canv.width,strokeWidth:4,keypoints:true});
    div.innerHTML = v;
    
  }
  loop();

}


function fromUnicode(){

  canv.width = 270;
  canv.height = 150;
  var t1 = ""; var t2 = "";

  function ct1(){
    setTimeout(ct1,1)
    t1 = String.fromCharCode(0x4e00+Math.floor(Math.random()*(0x9fff-0x4e00)));
  }
  function ct2(){
    setTimeout(ct2,1000);
    t2 = String.fromCharCode(0x4e00+Math.floor(Math.random()*(0x9fff-0x4e00)));
  }

  ct1();
  ct2();

  function loop(){
    animReq = requestAnimationFrame(loop);
    ctx.fillStyle="black";
    ctx.fillRect(0,0,300,300);
    ctx.fillStyle="white"
    if (Math.random)
    ctx.font="160px Kaiti TC";
    ctx.fillText(t1,0,120);
    ctx.fillText(t2,105,120);

    var imdata = ctx.getImageData(0,0,canv.width,canv.height);
    var data = imdata.data;
    var str = "";
    for (var i = 0; i < data.length; i+=4){
      if (data[i] > 128){
        str += "\1";
      }else{
        str += "\0";
      }
    }

    var s = TraceSkeleton.fromCharString(str,canv.width,canv.height);
    // console.log(s)
    var v = TraceSkeleton.visualize(s,{scale:DWIDTH/canv.width,strokeWidth:4,keypoints:true});
    div.innerHTML = v;
    
  }
  loop();
}


function fromDrawing(url){
  if (url){
    var img = new Image();
    img.src = url;
    img.onload = function(){
      canv.width = img.width;
      canv.height = img.height;
      ctx.drawImage(img,0,0);
    }
  }
  canv.width = 200;
  canv.height = 200;
  var mouseX =-1;
  var mouseY =-1;
  var pmouseX=-1;
  var pmouseY=-1;
  canv.addEventListener("mousemove",function(e){
    if (mouseX != -1){
      pmouseX = mouseX;
      pmouseY = mouseY;
    }
    mouseX = (e.clientX-canv.offsetLeft+window.scrollX)/(DWIDTH/canv.width);
    mouseY = (e.clientY-canv.offsetTop+window.scrollY)/(DWIDTH/canv.width);
  })
  ctx.fillRect(0,0,canv.width,canv.height);
  function loop(){
    animReq = requestAnimationFrame(loop);
    ctx.strokeStyle="white";
    // ctx.lineWidth=Math.min(Math.max(Math.sqrt(Math.pow(pmouseX-mouseX,2)+Math.pow(pmouseY-mouseY,2))*1,2),10);
    ctx.lineWidth=8;
    ctx.lineCap="round"
    if (pmouseX !=-1){
      ctx.beginPath();
      ctx.moveTo(pmouseX,pmouseY);
      ctx.lineTo(mouseX,mouseY);
      ctx.stroke();
    }

    var imdata = ctx.getImageData(0,0,canv.width,canv.height);
    var data = imdata.data;
    var str = "";
    for (var i = 0; i < data.length; i+=4){
      var x = (i/4)%canv.width;
      var y = Math.floor((i/4)/canv.width);
      if (x<1||x>canv.width-2||y<1||y>canv.height-2){
        str+="\0";
        continue;
      }
      if (data[i] > 128){
        str += "\1";
      }else{
        str += "\0";
      }
    }
    var s = TraceSkeleton.fromCharString(str,canv.width,canv.height);
    var v = TraceSkeleton.visualize(s,{scale:DWIDTH/canv.width,strokeWidth:4,keypoints:true});
    div.innerHTML = v;

    
  }
  loop();
}
(function adjustReadme(){
  document.getElementById("readme").style.paddingTop=(canv.offsetHeight+50)+"px"
  setTimeout(adjustReadme,500);
})()
fetch('README.md').then(function (response) {
  return response.text();
}).then(function (data) {
  document.getElementById("readme").innerHTML = 
    ("Available in all your favorite programming languages! C, C++, Java, JavaScript, Python, C#/Unity, Processing, OpenFrameworks. Check out the [Github repo](https://github.com/LingDong-/skeleton-tracing).\n\n"
    +"##"+data.split("##").slice(1).join("##"))
    .replace(/\[(.*?)\]\((.*?)\)/g,`<a href="$2">$1</a>`);

}).catch(function (err) {
  console.warn('Something went wrong.', err);
});

  // fromDrawing();
  fromDrawing("test_images/horse_r.png")
  document.getElementById("sel").onchange = function(){
    cancelAnimationFrame(animReq);

    var v = document.getElementById("sel").value;
    if (v=="1"){
      fromImageUrl("test_images/opencv-thinning-src-img.png")
    }else if (v=="2"){
      fromWebcam();
    }else if (v=="3"){
      fromUnicode();
    }else if (v=="4"){
      fromDrawing();
    }else if (v=="5"){
      fromDrawing("test_images/horse_r.png");
    }
  }
  document.getElementById("chk").onclick=function(){
    var c = document.getElementById("chk").checked;
    if (c){
      TraceSkeleton=TraceSkeletonWASM;
    }else{
      TraceSkeleton=TraceSkeletonVanilla;
    }
  }
</script>